var crypto = require('crypto');
var jsbip39 = require('./jsbip39');
var lan = require('./wordlist_english');
var Levenshtein = require('./levenshtein');
var bitcoinjs = require('./bitcoinjs-3.3.2');
var entropylib = require('./entropy');
var sjcl = require('./sjcl-bip39');
var biginteger = require('./biginteger');
var utils = require('../utils');
var ethUtil = require('./ethereumjs-util');
var EthereumTx = require('ethereumjs-tx');
var litecore = require('litecore-lib');
var log4js = require("log4js");
var config = require('../config.json');
const BigNumber = require('bignumber.js');


log4js.configure(config);
const logger = log4js.getLogger('api');

var mnemonics = { "english": new jsbip39.Mnemonic("english") };
var mnemonic = mnemonics["english"];
var network = bitcoinjs.bitcoin.networks.bitcoin;

var seed = null;
var bip32RootKey = null;
var bip32ExtendedKey = null;

var extendedPrivKey = null;
var extendedPubKey = null;

var generationProcesses = [];
var accounts = [];

var coin = 0;
var currentNetwork = null;
var bchAddrType = null;
var response = {};


function recoverAddress (symbol, mnemonic, language, startIndex, amount, additional){
    try{
        // symbol, mnemonic, language, startIndex, amount, additional
        logger.info("start generateAddress, symbol[%s], language[%s], startIndex[%d], amount[%d], additional[%s]",
            symbol, language, startIndex, amount, additional);
        // getLanguageWordlist(language);
        networkChanged(symbol);

        differenceProcessing(symbol, additional);

        phraseChanged(language, mnemonic, true, startIndex, amount);
        response["bip32RootKey"] = bip32RootKey.toBase58();
        response["extendedPrivKey"] = extendedPrivKey;
        response["extendedPubKey"] = extendedPubKey;
        response["accounts"] = accounts;
        logger.info("generate account info success");

    }catch (e) {
        logger.error(e);
        throw e  ;
    }



}

function getLanguageWordlist(language) {
    if (language === "english") {
        lan = require('./wordlist_english');
    } else if (language === "chinese_simplified") {
        lan = require('./wordlist_chinese_simplified');
    } else {
        logger.error("Unsupported language: " + language);
        var err = "Unsupported language: " + language;
        throw err;
    }
}

function clearGlobalData() {
    //由于只有一个客户端，所以增加清除全局变量的方法是可以的。

    network = bitcoinjs.bitcoin.networks.bitcoin;
    seed = null;
    bip32RootKey = null;
    bip32ExtendedKey = null;
    extendedPrivKey = null;
    extendedPubKey = null;
    generationProcesses = [];
    accounts = [];
    coin = 0;
    currentNetwork = null;
    response = {};
    bchAddrType = null;
}

function generateMnemonic(mnemonicLength, language) {

    logger.info("start generateMnemonic, mnemonicLength[%d], language[%s]", mnemonicLength, language);
    getLanguageWordlist(language);
    setMnemonicLanguage(language);
    //使用随机数组的方式生成助记词
    //var words = generateRandomPhrase();
    //使用 熵 的方式生成助记词
    var words = setMnemonicFromEntropy(mnemonicLength);
    if (!words) {
        logger.error("Failed to generate mnemonic");
        err = "Failed to generate mnemonic";
        throw err;
    }
    logger.info("generate mnemonic success");
    // console.log(words);
    // phraseChanged(language, words, false, 0, 0);

    // logger.info("generate seed success");
    // console.log(seed);
    // console.log(bip32RootKey);
    // console.log(bip32RootKey.toBase58());

    // response["seed"] = seed;
    response["mnemonic"] = words;

    logger.debug("mnemonic[%s]", words);
}

function generateAddress(symbol, mnemonic, language, startIndex, amount, additional) {

    try{
        logger.info("start generateAddress, symbol[%s], language[%s], startIndex[%d], amount[%d], additional[%s]",
            symbol, language, startIndex, amount, additional);
        // getLanguageWordlist(language);
        networkChanged(symbol);

        differenceProcessing(symbol, additional);

        phraseChanged(language, mnemonic, true, startIndex, amount);

        response["bip32RootKey"] = bip32RootKey.toBase58();
        response["extendedPrivKey"] = extendedPrivKey;
        response["extendedPubKey"] = extendedPubKey;
        response["accounts"] = accounts;
        logger.info("generate account info success");


    }catch (e) {
        throw e ;
    }




}

// 部分币种有差异化处理
function differenceProcessing(symbol, additional) {
    if (symbol == "BCH") {
        bchAddrType = additional;
    } else if (symbol == "LTC") {
        if (additional) {
            network = bitcoinjs.bitcoin.networks.litecoinXprv;
        }
        else {
            network = bitcoinjs.bitcoin.networks.litecoin;
        }
    }
}

// 不同币种的网络参数不一样
function networkChanged(symbol) {
    for (var i = 0; i < networks.length; i++) {
        if (networks[i].name == symbol) {
            currentNetwork = networks[i];
            currentNetwork.onSelect();
            adjustNetworkForSegwit();
            break;
        }
    }

    if (currentNetwork == null) {
        var err = "Does not support this currency";
        throw err;
    }
}


function adjustNetworkForSegwit() {
    // If segwit is selected the xpub/xprv prefixes need to be adjusted
    // to avoid accidentally importing BIP49 xpub to BIP44 watch only
    // wallet.
    // See https://github.com/iancoleman/bip39/issues/125
    var segwitNetworks = null;
    // if a segwit network is alread selected, need to use base network to
    // look up new parameters
    if ("baseNetwork" in network) {
        network = bitcoinjs.bitcoin.networks[network.baseNetwork];
    }
    // choose the right segwit params
    // if (p2wpkhSelected() && "p2wpkh" in network) {
    //     network = network.p2wpkh;
    // }
    // else if (p2wpkhInP2shSelected() && "p2wpkhInP2sh" in network) {
    //     network = network.p2wpkhInP2sh;
    // }
}


function setMnemonicLanguage(language) {
    // Load the bip39 mnemonic generator for this language if required
    if (!(language in mnemonics)) {
        mnemonics[language] = new jsbip39.Mnemonic(language);
    }
    mnemonic = mnemonics[language];
}

// function generateRandomPhrase() {
//     // get the amount of entropy to use
//     var numWords = 12;
//     var strength = numWords / 3 * 32;
//     var buffer = new Uint8Array(strength / 8);
//     // create secure entropy
//     //生成随机数量，这种方式不太可靠，得找新的方法
//     var buffer = crypto.randomBytes(strength / 8);
//
//     console.log(buffer);
//     // show the words
//     var words = mnemonic.toMnemonic(buffer);
//     // show the entropy
//     var entropyHex = uint8ArrayToHex(buffer);
//     console.log(entropyHex);
//     return words;
// }

// function uint8ArrayToHex(a) {
//     var s = ""
//     for (var i=0; i<a.length; i++) {
//         var h = a[i].toString(16);
//         while (h.length < 2) {
//             h = "0" + h;
//         }
//         s = s + h;
//     }
//     return s;
// }


function phraseChanged(language, words, isGenerateAddress, startIndex, amount) {
    setMnemonicLanguage(language);
    var errorText = findPhraseErrors(language, words);
    if (errorText) {
        logger.error(errorText);
        var err = errorText;
        throw err;
    }
    try {
        // TODO 这是密码， 目前写死为空。如果需要，可通过参数传过来。
        var passphrase = '';
        calcBip32RootKeyFromSeed(words, passphrase);
    }
    catch (err) {
        logger.error(err);
        throw err;
    }
    try {
        if (isGenerateAddress) {
            calcForDerivationPath(startIndex, amount);
        }
    }
    catch (err) {
        logger.error(err);
        throw err;
    }
}

// 检查助记词是否有错
function findPhraseErrors(language, phrase) {
    // Preprocess the words
    phrase = mnemonic.normalizeString(phrase);
    var words = phraseToWordArray(phrase);
    // Detect blank phrase
    if (words.length == 0) {
        return "Blank mnemonic";
    }
    // Check each word
    for (var i=0; i<words.length; i++) {
        var word = words[i];
        // var language = getLanguage();
        if (lan.WORDLISTS[language].indexOf(word) == -1) {
            logger.warn("Finding closest match to " + word);
            var nearestWord = findNearestWord(language, word);
            return word + " not in wordlist, did you mean " + nearestWord + "?";
        }
    }
    // Check the words are valid
    var properPhrase = wordArrayToPhrase(words);
    var isValid = mnemonic.check(properPhrase);
    if (!isValid) {
        return "Invalid mnemonic";
    }
    return false;
}

// TODO look at jsbip39 - mnemonic.splitWords
function phraseToWordArray(phrase) {
    var words = phrase.split(/\s/g);
    var noBlanks = [];
    for (var i=0; i<words.length; i++) {
        var word = words[i];
        if (word.length > 0) {
            noBlanks.push(word);
        }
    }
    return noBlanks;
}

function findNearestWord(language, word) {
    // var language = getLanguage();
    var words = lan.WORDLISTS[language];
    var minDistance = 99;
    var closestWord = words[0];
    for (var i=0; i<words.length; i++) {
        var comparedTo = words[i];
        if (comparedTo.indexOf(word) == 0) {
            return comparedTo;
        }
        var distance = Levenshtein.get(word, comparedTo);
        if (distance < minDistance) {
            closestWord = comparedTo;
            minDistance = distance;
        }
    }
    return closestWord;
}

// TODO look at jsbip39 - mnemonic.joinWords
function wordArrayToPhrase(words) {
    var phrase = words.join(" ");
    var language = getLanguageFromPhrase(phrase);
    if (language == "japanese") {
        phrase = words.join("\u3000");
    }
    return phrase;
}


function getLanguageFromPhrase(phrase) {
    // Check if how many words from existing phrase match a language.
    var language = "";
    if (!phrase) {
        return;
    }
    if (phrase.length > 0) {
        var words = phraseToWordArray(phrase);
        var languageMatches = {};
        for (l in lan.WORDLISTS) {
            // Track how many words match in this language
            languageMatches[l] = 0;
            for (var i=0; i<words.length; i++) {
                var wordInLanguage = lan.WORDLISTS[l].indexOf(words[i]) > -1;
                if (wordInLanguage) {
                    languageMatches[l]++;
                }
            }
            // Find languages with most word matches.
            // This is made difficult due to commonalities between Chinese
            // simplified vs traditional.
            var mostMatches = 0;
            var mostMatchedLanguages = [];
            for (var l in languageMatches) {
                var numMatches = languageMatches[l];
                if (numMatches > mostMatches) {
                    mostMatches = numMatches;
                    mostMatchedLanguages = [l];
                }
                else if (numMatches == mostMatches) {
                    mostMatchedLanguages.push(l);
                }
            }
        }
        if (mostMatchedLanguages.length > 0) {
            // Use first language and warn if multiple detected
            language = mostMatchedLanguages[0];
            if (mostMatchedLanguages.length > 1) {
                console.warn("Multiple possible languages");
                console.warn(mostMatchedLanguages);
            }
        }
    }
    return language;
}

function calcBip32RootKeyFromSeed(phrase, passphrase) {
    try{
        seed = mnemonic.toSeed(phrase, passphrase);
        bip32RootKey = bitcoinjs.bitcoin.HDNode.fromSeedHex(seed, network);
    }catch (e) {
        throw e ;
    }

    // console.log(seed);
    // console.log(bip32RootKey);
    // console.log(bip32RootKey.toBase58());
}


function calcForDerivationPath(startIndex, amount) {

    // Get the derivation path
    var derivationPath = getDerivationPath("bip44", coin, "0", "0");
    var errorText = findDerivationPathErrors(derivationPath);
    if (errorText) {
        throw errorText;
    }
    bip32ExtendedKey = calcBip32ExtendedKey(derivationPath);

    displayBip32Info(startIndex, amount);
}


function getDerivationPath(selectd, coin, account, change) {

    if (selectd === "bip44") {
        var purpose = "44";
        var path = "m/";
        path += purpose + "'/";
        path += coin + "'/";
        path += account + "'/";
        path += change;
        // console.log("Using derivation path from BIP44 tab: " + path);
        return path;
    }
    else if (selectd === "bip49") {
        var purpose = "49";
        var path = "m/";
        path += purpose + "'/";
        path += coin + "'/";
        path += account + "'/";
        path += change;
        // console.log("Using derivation path from BIP49 tab: " + path);
        return path;
    }
    else if (selectd === "bip84") {
        var purpose = "84";
        var path = "m/";
        path += purpose + "'/";
        path += coin + "'/";
        path += account + "'/";
        path += change;
        // console.log("Using derivation path from BIP84 tab: " + path);
        return path;
    }
    else if (selectd === "bip32") {
        var derivationPath = "m/0";
        // console.log("Using derivation path from BIP32 tab: " + derivationPath);
        return derivationPath;
    }
    else if (selectd === "bip141") {
        var derivationPath = "m/0";
        // console.log("Using derivation path from BIP141 tab: " + derivationPath);
        return derivationPath;
    }
    else {
        var err = "Unknown derivation path";
        throw err;
    }
}


function findDerivationPathErrors(path) {
    // TODO is not perfect but is better than nothing
    // Inspired by
    // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vectors
    // and
    // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#extended-keys
    var maxDepth = 255; // TODO verify this!!
    var maxIndexValue = Math.pow(2, 31); // TODO verify this!!
    if (path[0] != "m") {
        return "First character must be 'm'";
    }
    if (path.length > 1) {
        if (path[1] != "/") {
            return "Separator must be '/'";
        }
        var indexes = path.split("/");
        if (indexes.length > maxDepth) {
            return "Derivation depth is " + indexes.length + ", must be less than " + maxDepth;
        }
        for (var depth = 1; depth<indexes.length; depth++) {
            var index = indexes[depth];
            var invalidChars = index.replace(/^[0-9]+'?$/g, "")
            if (invalidChars.length > 0) {
                return "Invalid characters " + invalidChars + " found at depth " + depth;
            }
            var indexValue = parseInt(index.replace("'", ""));
            if (isNaN(depth)) {
                return "Invalid number at depth " + depth;
            }
            if (indexValue > maxIndexValue) {
                return "Value of " + indexValue + " at depth " + depth + " must be less than " + maxIndexValue;
            }
        }
    }
    // Check root key exists or else derivation path is useless!
    if (!bip32RootKey) {
        return "No root key";
    }
    // Check no hardened derivation path when using xpub keys
    var hardenedPath = path.indexOf("'") > -1;
    //var hardenedAddresses = bip32TabSelected() && DOM.hardenedAddresses.prop("checked");
    var hardenedAddresses = false;
    var hardened = hardenedPath || hardenedAddresses;
    var isXpubkey = bip32RootKey.isNeutered();
    if (hardened && isXpubkey) {
        return "Hardened derivation path is invalid with xpub key";
    }
    return false;
}


function calcBip32ExtendedKey(path) {
    // Check there's a root key to derive from
    if (!bip32RootKey) {
        return bip32RootKey;
    }
    var extendedKey = bip32RootKey;
    // Derive the key from the path
    var pathBits = path.split("/");
    for (var i=0; i<pathBits.length; i++) {
        var bit = pathBits[i];
        var index = parseInt(bit);
        if (isNaN(index)) {
            continue;
        }
        var hardened = bit[bit.length-1] == "'";
        var isPriv = !(extendedKey.isNeutered());
        var invalidDerivationPath = hardened && !isPriv;
        if (invalidDerivationPath) {
            extendedKey = null;
        }
        else if (hardened) {
            extendedKey = extendedKey.deriveHardened(index);
        }
        else {
            extendedKey = extendedKey.derive(index);
        }
    }
    return extendedKey
}

function displayBip32Info(startIndex, amount) {
    // Display the key
    var rootKey = bip32RootKey.toBase58();
    var xprvkeyB58 = "NA";
    if (!bip32ExtendedKey.isNeutered()) {
        xprvkeyB58 = bip32ExtendedKey.toBase58();
    }
    extendedPrivKey = xprvkeyB58;
    extendedPubKey = bip32ExtendedKey.neutered().toBase58();

    var initialAddressCount = parseInt(amount);
    displayAddresses(startIndex, initialAddressCount);

    // console.log(rootKey);
    // console.log(extendedPrivKey);
    // console.log(extendedPubKey);
}

function displayAddresses(start, total) {
    generationProcesses.push(new (function() {
        var rows = [];
        this.stop = function() {
            for (var i=0; i<rows.length; i++) {
                rows[i].shouldGenerate = false;
            }
        };

        for (var i=0; i<total; i++) {
            var index = i + start;
            var isLast = i == total - 1;
            rows.push(new TableRow(index, isLast));
        }
    })());
}


function TableRow(index, isLast) {

    var self = this;
    this.shouldGenerate = true;
    //var useHardenedAddresses = DOM.hardenedAddresses.prop("checked");
    var useHardenedAddresses = false;
    //var useBip38 = DOM.useBip38.prop("checked");
    //var bip38password = DOM.bip38Password.val();
    var useBip38 = false;
    var bip38password = "";
    // var isSegwit = segwitSelected();
    var isSegwit = false;
    var segwitAvailable = networkHasSegwit();
    // var isP2wpkh = p2wpkhSelected();
    // var isP2wpkhInP2sh = p2wpkhInP2shSelected();
    var isP2wpkh = false;
    var isP2wpkhInP2sh = false;

    function init() {
        calculateValues();
    }

    function calculateValues() {
            if (!self.shouldGenerate) {
                return;
            }
            // derive HDkey for this row of the table
            var key = "NA";
            if (useHardenedAddresses) {
                key = bip32ExtendedKey.deriveHardened(index);
            }
            else {
                key = bip32ExtendedKey.derive(index);
            }
            // bip38 requires uncompressed keys
            // see https://github.com/iancoleman/bip39/issues/140#issuecomment-352164035
            var keyPair = key.keyPair;
            var useUncompressed = useBip38;
            if (useUncompressed) {
                keyPair = new bitcoinjs.bitcoin.ECPair(keyPair.d, null, { network: network, compressed: false });
            }
            // get address
            var address = keyPair.getAddress().toString();
            // get privkey
            var hasPrivkey = !key.isNeutered();
            var privkey = "NA";
            if (hasPrivkey) {
                privkey = keyPair.toWIF();
                // BIP38 encode private key if required
                if (useBip38) {
                    privkey = bitcoinjsBip38.encrypt(keyPair.d.toBuffer(), false, bip38password, function(p) {
                        console.log("Progressed " + p.percent.toFixed(1) + "% for index " + index);
                    });
                }
            }
            // get pubkey
            var pubkey = keyPair.getPublicKeyBuffer().toString('hex');
            var indexText = getDerivationPath("bip44", coin, "0", "0") + "/" + index;
            if (useHardenedAddresses) {
                indexText = indexText + "'";
            }
            // Ethereum values are different
            if ((currentNetwork.name == "ETH")
            || (currentNetwork.name == "ETC")) {
                var privKeyBuffer = keyPair.d.toBuffer(32);
                privkey = privKeyBuffer.toString('hex');
                var addressBuffer = ethUtil.privateToAddress(privKeyBuffer);
                var hexAddress = addressBuffer.toString('hex');
                var checksumAddress = ethUtil.toChecksumAddress(hexAddress);
                address = ethUtil.addHexPrefix(checksumAddress);
                privkey = ethUtil.addHexPrefix(privkey);
                pubkey = ethUtil.addHexPrefix(pubkey);

            }
            // Ripple values are different
            if (currentNetwork.name == "XRP") {
                var rippleUtil = require('./ripple-util');
                privkey = rippleUtil.convertRipplePriv(privkey);
                address = rippleUtil.convertRippleAdrr(address);
            }
            // Bitcoin Cash address format may vary
            if (currentNetwork.name == "BCH") {
                var bchaddr = require('./bchaddrjs-0.2.1');
                // var bchAddrType ;
                if (bchAddrType == "cashaddr") {
                    address = bchaddr.toCashAddress(address);
                }
                else if (bchAddrType == "bitpay") {
                    address = bchaddr.toBitpayAddress(address);
                }
            }
            // Segwit addresses are different
            if (isSegwit) {
                if (!segwitAvailable) {
                    return;
                }
                if (isP2wpkh) {
                    var keyhash = bitcoinjs.bitcoin.crypto.hash160(key.getPublicKeyBuffer());
                    var scriptpubkey = bitcoinjs.bitcoin.script.witnessPubKeyHash.output.encode(keyhash);
                    address = bitcoinjs.bitcoin.address.fromOutputScript(scriptpubkey, network)
                }
                else if (isP2wpkhInP2sh) {
                    var keyhash = bitcoinjs.bitcoin.crypto.hash160(key.getPublicKeyBuffer());
                    var scriptsig = bitcoinjs.bitcoin.script.witnessPubKeyHash.output.encode(keyhash);
                    var addressbytes = bitcoinjs.bitcoin.crypto.hash160(scriptsig);
                    var scriptpubkey = bitcoinjs.bitcoin.script.scriptHash.output.encode(addressbytes);
                    address = bitcoinjs.bitcoin.address.fromOutputScript(scriptpubkey, network)
                }
            }

            var account = {};
            account["path"] = indexText;
            account["address"] = address;
            account["pubkey"] = pubkey;
            account["privkey"] = privkey;

            accounts.push(account);
    }

    init();

}

function networkHasSegwit() {
    var n = network;
    if ("baseNetwork" in network) {
        n = bitcoinjs.bitcoin.networks[network.baseNetwork];
    }
    // // check if only p2wpkh params are required
    // if (p2wpkhSelected()) {
    //     return "p2wpkh" in n;
    // }
    // // check if only p2wpkh-in-p2sh params are required
    // else if (p2wpkhInP2shSelected()) {
    //     return "p2wpkhInP2sh" in n;
    // }
    // require both if it's unclear which params are required
    return "p2wpkh" in n && "p2wpkhInP2sh" in n;
}

function getEntropy() {
    //TODO:需要寻找更安全的熵生成方法，比如 依赖硬件的相关参数生成随机数 等方法。
    var sha512 = crypto.createHash('sha512');

    var md5 = crypto.createHash('md5');
    var myDate = new Date();
    var hrTime1 = process.hrtime();
    var rand = Math.random().toString(36).substr(2);
    var src = myDate.toLocaleString() + " " + rand + " " + hrTime1.toString();
    var result = md5.update(src).digest('hex');
    sha512.update(src + result);

    return sha512.digest('hex');
}

function setMnemonicFromEntropy(mnemonicLength) {
    // Get entropy value
    var entropyStr = getEntropy();
    // Work out minimum base for entropy
     var entropyInstance = new entropylib.Entropy();
    // var entropy = entropylib.Entropy.fromString(entropyStr);
    var entropy = entropyInstance.fromString(entropyStr);
    if (entropy.binaryStr.length == 0) {
        logger.error("entropy.binaryStr.length = 0");
        return;
    }
    // Show entropy details
   // showEntropyFeedback(entropy);
    // Use entropy hash if not using raw entropy
    var bits = entropy.binaryStr;
    //var mnemonicLength = 12;

    // Get bits by hashing entropy with SHA256
    var hash = sjcl.hash.sha256.hash(entropy.cleanStr);
    var hex = sjcl.codec.hex.fromBits(hash);
    bits = biginteger.BigInteger.parse(hex, 16).toString(2);
    while (bits.length % 256 != 0) {
        bits = "0" + bits;
    }
    // Truncate hash to suit number of words
    //mnemonicLength = parseInt(mnemonicLength);
    var numberOfBits = 32 * mnemonicLength / 3;
    bits = bits.substring(0, numberOfBits);
    // show warning for weak entropy override
    // if (mnemonicLength / 3 * 32 > entropy.binaryStr.length) {
    //     DOM.entropyWeakEntropyOverrideWarning.removeClass("hidden");
    // }
    // else {
    //     DOM.entropyWeakEntropyOverrideWarning.addClass("hidden");
    // }

    // Discard trailing entropy
    var bitsToUse = Math.floor(bits.length / 32) * 32;
    var start = bits.length - bitsToUse;
    var binaryStr = bits.substring(start);
    // Convert entropy string to numeric array
    var entropyArr = [];
    for (var i=0; i<binaryStr.length / 8; i++) {
        var byteAsBits = binaryStr.substring(i*8, i*8+8);
        var entropyByte = parseInt(byteAsBits, 2);
        entropyArr.push(entropyByte)
    }

    // Convert entropy array to mnemonic
    var phrase = mnemonic.toMnemonic(entropyArr);
    // Set the mnemonic in the UI
    // DOM.phrase.val(phrase);
    // // Show the word indexes
    // showWordIndexes();
    // // Show the checksum
    // showChecksum();
    return phrase;
}

function setHdCoin(coinValue) {
    coin = coinValue;
}

var networks = [
    {
        name: "BCH",    //比特现金
        onSelect: function() {
            setHdCoin(145);
        },
    },
    {
        name: "BTC",    //比特币
        onSelect: function() {
            network = bitcoinjs.bitcoin.networks.bitcoin;
            setHdCoin(0);
        },
    },
    {
        name: "BTC-T",  //比特币测试网
        onSelect: function() {
            network = bitcoinjs.bitcoin.networks.testnet;
            setHdCoin(1);
        },
    },
    {
        name: "BTG",    //比特黄金
        onSelect: function() {
            network = bitcoinjs.bitcoin.networks.bgold;
            setHdCoin(156);
        },
    },
    {
        name: "DASH",   //达世币
        onSelect: function() {
            network = bitcoinjs.bitcoin.networks.dash;
            setHdCoin(5);
        },
    },
    {
        name: "DOGE",   //狗狗币
        onSelect: function() {
            network = bitcoinjs.bitcoin.networks.dogecoin;
            setHdCoin(3);
        },
    },
    {
        name: "ETC",    //以太经典
        segwitAvailable: false,
        onSelect: function() {
            network = bitcoinjs.bitcoin.networks.bitcoin;
            setHdCoin(61);
        },
    },
    {
        name: "ETH",    //以太访
        onSelect: function() {
            network = bitcoinjs.bitcoin.networks.bitcoin;
            setHdCoin(60);
        },
    },
    {
        name: "LTC",    //莱特币
        onSelect: function() {
            network = bitcoinjs.bitcoin.networks.litecoin;
            setHdCoin(2);
        },
    },
    {
        name: "XRP",    //瑞波币
        onSelect: function() {
            network = bitcoinjs.bitcoin.networks.bitcoin;
            setHdCoin(144);
        },
    },
    {
        name: "ZEC",    //Zcash 大零币
        onSelect: function() {
            network = bitcoinjs.bitcoin.networks.zcash;
            setHdCoin(133);
        },
    },
];

bitcoinjs.bitcoin.networks.bgold = {
    messagePrefix: '\x1DBitcoin Gold Signed Message:\n',
    bip32: {
        public: 0x0488b21e,
        private: 0x0488ade4
    },
    pubKeyHash: 38,
    scriptHash: 23,
    wif: 128
};


bitcoinjs.bitcoin.networks.dash = {
    messagePrefix: 'unused',
    bip32: {
        public: 0x0488b21e,
        private: 0x0488ade4
    },
    pubKeyHash: 0x4c,
    scriptHash: 0x10,
    wif: 0xcc
};

bitcoinjs.bitcoin.networks.dogecoin = {
    messagePrefix: '\x19Dogecoin Signed Message:\n',
    bip32: {
        public: 0x02facafd,
        private: 0x02fac398
    },
    pubKeyHash: 0x1e,
    scriptHash: 0x16,
    wif: 0x9e
};


bitcoinjs.bitcoin.networks.zcash = {
    messagePrefix: '\x18Zcash Signed Message:\n',
    bip32: {
        public: 0x0488B21E,
        private: 0x0488ADE4,
    },
    pubKeyHash: 0x1CB8,
    scriptHash: 0x1CBD,
    wif: 0x80,
};

bitcoinjs.bitcoin.networks.litecoinXprv = {
    messagePrefix: '\x19Litecoin Signed Message:\n',
    bip32: {
        public: 0x0488b21e,
        private: 0x0488ade4,
    },
    pubKeyHash: 0x30,
    scriptHash: 0x32,
    wif: 0xb0
};

function signETHTx(txData, privateKey) {
    try {
        const tx = new EthereumTx(txData);
        tx.sign(Buffer.from(privateKey, 'hex'));
        const serializedTx = tx.serialize();

        response["txid"] = "0x" + tx.hash().toString('hex');
        response["rawTx"] = "0x" + serializedTx.toString('hex');
    }
    catch (e) {
        logger.error(e);
        throw e;
    }
}
// signBTCTxNEO("BTC" ,[ { privateKey: 'KwunAbQT6usgzxSuwtWMP5Jn9JxbvLGPB2ovQpMAVSdqDoetohX2', satoshis: '1e+13',txid: 'c36864ac033377cde64f69e77dbf306fc7daebae0db4259bfee880e1a19b2851',vout: '1' } ],
//     [ { address: '1F1aBtG5BDefGeMsh7c1JJRmAN7TyXgtjB',
//     amount: '1000000000' }],5)
function signBTCTxNEO(symbol,input,output,fee){
console.log(symbol,"______________",input,"_________________",output,fee);
        networkChanged(symbol);
        // console.error(symbol,input,output);
        try {
            var bitcoinjsLib = require('bitcoinjs-lib');
            var alices = [];

            let amount = 0;
            for (let i in output) {
                let num = BigNumber(output[i].amount);
                amount +=num.toNumber();
            }

            let getValue = 0;
            let getUtxos = [];
            let returnV = -1;
            let totalCount = 0;
            var remarkSame = {};


            let vinCount = 0, voutCount = 1 + output.length;

            let getTotalFee = () => {
                let value = (10 + 148 * vinCount + 34 * voutCount) * parseInt(fee);   //from  https://bitcoin.stackexchange.com/questions/41443/transaction-priority
                return value
            };
            //获取utxos
            for (let index = 1; 1; index++) {
                if (input && input.length === 0) break;
                input.forEach((ut) => {
                    if (returnV < 0) {
                        if(!remarkSame[ut.txid]){  //相同的txid， 发送交易时会报错，所以同一个交易不能放相同的txid
                            let num = BigNumber(ut.satoshis)
                            getValue += +num.toNumber();
                            getUtxos.push(ut);
                            vinCount++;
                            returnV = getValue - ((amount) + getTotalFee());
                            remarkSame[ut.txid] = 1;
                            console.error(getValue,getUtxos,returnV);
                        }
                    }
                });

                if (returnV >= 0) break;
            }
            for(var i = 0; i < getUtxos.length; i++) {
                alices.push(bitcoinjsLib.ECPair.fromWIF(getUtxos[i].privateKey, network));
            }

            const txb = new bitcoinjsLib.TransactionBuilder(network);

            //TODO 交易版本设置为 1， 但并未了解参数意思。 我发现有些交易版本是 2 。得去了解这一参数意义。
            txb.setVersion(1);

            for(let i = 0; i < getUtxos.length; i++) {
                txb.addInput(getUtxos[i].txid, getUtxos[i].vout * 1);
            }

            for(var i = 0; i < output.length; i++) {
                txb.addOutput(output[i].address, output[i].amount * 1);
            }
            // console.error(input[0].address, returnV);

            //BTC 公钥转地址
            function getAddress(privateKey) {

                let publicKey = bitcoinjsLib.ECPair.fromWIF(privateKey, network).publicKey.toString("hex");
                console.error("+++++++++++++++++++++++",publicKey);
                return bitcoinjsLib.address.toBase58Check(bitcoinjsLib.crypto.hash160(Buffer.from(publicKey, 'hex')), network.pubKeyHash)
            }
            // BTC 的私钥 取公钥
            if (returnV > 0) {
                txb.addOutput(getAddress(input[0].privateKey), returnV);
            };

            for(let i = 0; i < alices.length; i++) {
                txb.sign(i, alices[i]);
            }
            response["txid"] = txb.build().getId();
            response["rawTx"] = txb.build().toHex();
        }catch (e) {
            logger.error(e);
            console.log(e);
            throw e;
        }

}


function signBTCTx(symbol, input, output) {
    // 修改网络，是为了兼容 比特币测试网 。如果不要测试网，去掉这一过程。
    networkChanged(symbol);
    console.error(symbol,"_____________________",input,"_____________________",output);
    try {
        var bitcoinjsLib = require('bitcoinjs-lib');
        var alices = [];
        for(var i = 0; i < input.length; i++) {
            alices.push(bitcoinjsLib.ECPair.fromWIF(input[i].privateKey, network));
        }

        const txb = new bitcoinjsLib.TransactionBuilder(network);

        //TODO 交易版本设置为 1， 但并未了解参数意思。 我发现有些交易版本是 2 。得去了解这一参数意义。
        txb.setVersion(1);

        for(var i = 0; i < input.length; i++) {
            txb.addInput(input[i].txid, input[i].vout * 1);
        }

        for(var i = 0; i < output.length; i++) {
            txb.addOutput(output[i].address, output[i].amount * 1);
        }

        for(var i = 0; i < alices.length; i++) {
            txb.sign(i, alices[i]);
        }
        response["txid"] = txb.build().getId();
        response["rawTx"] = txb.build().toHex();
    }
    catch (e) {
        logger.error(e);
        throw e;
    }
}

function signBTCMsg(privateKey, message) {
    try {

        var bitcoinMessage = require('bitcoinjs-message');
        var keyPair = bitcoinjs.bitcoin.ECPair.fromWIF(privateKey);
        var privateK = keyPair.d.toBuffer(32);
        var signature = bitcoinMessage.sign(message.toString(), privateK, keyPair.compressed);

        response["signMsg"] = signature.toString('base64');
        // console.log(response);
    }
    catch (e) {
        logger.error(e);
        throw e;
    }
}

function verifyBTCMsg(message, address, signature) {
    try {
        var bitcoinMessage = require('bitcoinjs-message');

        var verified = bitcoinMessage.verify(message.toString(), address, signature);

        response["verifyMsg"] = verified;
        // console.log(verified);
        return verified;
    }
    catch (e) {
        logger.error(e);
        throw e;
    }
}

function signLTCTx(privateKey, input, output) {
    try {
        var tx = new litecore.Transaction()
            .from(input)
            .to(output)
            .sign(privateKey);

        // console.log(tx.getFee());

        var txData = JSON.stringify(tx);
        var txObj = JSON.parse(txData);

        response["txid"] = txObj.hash;
        response["rawTx"] = tx.toString();

        // console.log(response);
    }
    catch (e) {
        logger.error(e);
        throw e;
    }
}

function signLTCMsg(privateKey, message) {
    try {
        var Message = require('litecore-message');

        var privateK = new litecore.PrivateKey(privateKey);
        var msg = new Message(message.toString());

        var signature = msg.sign(privateK);

        response["signMsg"] = signature.toString();
        // console.log(response);
    }
    catch (e) {
        logger.error(e);
        throw e;
    }
}

function verifyLTCMsg(message, address, signature) {
    try {
        var Message = require('litecore-message');
        var verified = new Message(message.toString()).verify(address, signature);

        response["verifyMsg"] = verified;
        // console.log(verified);
        return verified;
    }
    catch (e) {
        logger.error(e);
        throw e;
    }
}


function verifyAddress(symbol, address) {
    try {
        var verified = false;
        if (symbol == "ETH") {
            verified = ethUtil.isValidChecksumAddress(address);
        } else {
            networkChanged(symbol);

            var bitcoinjsLib = require('bitcoinjs-lib');
            var ret = bitcoinjsLib.address.fromBase58Check(address);

            if (ret.version == network.pubKeyHash) {
                verified = true;
            }
        }

        response["verifyAddress"] = verified;
        return verified;
    }
    catch (e) {
        logger.error(e);
        throw e;
    }
}


function countFeeCost (inputs,outputs,type,fee) {
        try{
            if ( inputs.length === 0 && output.length === 0){
                return resolve({"data":null,"success":false,"error":"invalid Parameter","code": 10002});
            }
            let data = {};
            let vinCount = 0, voutCount = 1 + outputs.length;
            let getTotalFee = () => {
                return  (10 + 148 * vinCount + 34 * voutCount) * fee;   //from  https://bitcoin.stackexchange.com/questions/41443/transaction-priority
            };

            let sameMark = {};
            let balance = 0;
            let amounts =  0;
            for(let i in outputs){
                let output = outputs[i];
                amounts += output.amount;
            }
            let returnV=0;
            var feeCost=0;
            // console.log(feeCost);
            if(type === "all"){
                for (let i in inputs) {
                    let input = inputs[i];
                    if(!sameMark[input.txid] && returnV >=0){
                        sameMark[input.txid] = 1;
                        balance += input.satoshis;
                        vinCount++;
                        feeCost = getTotalFee();
                        returnV = balance - (amounts-feeCost);
                    }
                }
            }else{
                for (let i in inputs) {
                    let input = inputs[i];
                    if(!sameMark[input.txid]){
                        sameMark[input.txid] = 1;
                        vinCount++;
                        feeCost = getTotalFee();
                    }
                }
            }

            response["feeCost"]= feeCost;
        }catch (e) {
           logger.error(e);
           throw e;
        }



}
// 消息处理 根据不同的method，走不同的分支，处理不同的币和业务
// 有关 地址部分 的内容，参考  https://github.com/iancoleman/bip39 开源项目， 且做了删减 。 可对比别人家的代码看这份代码。
exports.handleMsg = function (dataObj) {

    try {
        clearGlobalData();
        utils.checkUndefine(dataObj.method);
        utils.checkUndefine(dataObj.params);

        if (dataObj.method === "generateMnemonic") {
            utils.checkUndefine(dataObj.params.length);
            utils.checkUndefine(dataObj.params.language);

            generateMnemonic(dataObj.params.length * 1, dataObj.params.language);

        } else if (dataObj.method === "generateAddress") {
            utils.checkUndefine(dataObj.params.symbol);
            utils.checkUndefine(dataObj.params.mnemonic);
            utils.checkUndefine(dataObj.params.language);
            utils.checkUndefine(dataObj.params.amount);
            utils.checkUndefine(dataObj.params.startIndex);

            generateAddress(dataObj.params.symbol.toLocaleUpperCase(), dataObj.params.mnemonic, dataObj.params.language, dataObj.params.startIndex * 1, dataObj.params.amount * 1, dataObj.params.additional);
        } else if (dataObj.method === "signTx") {
            utils.checkUndefine(dataObj.params.symbol);
            if (dataObj.params.symbol.toLocaleUpperCase() === "ETH") {
                utils.checkUndefine(dataObj.params.txData);
                utils.checkUndefine(dataObj.params.privateKey);

                signETHTx(dataObj.params.txData, dataObj.params.privateKey);

            } else if ((dataObj.params.symbol.toLocaleUpperCase() === "BTC") ||
                (dataObj.params.symbol.toLocaleUpperCase() === "BTC-T")) {

                // console.error(dataObj.params);

                utils.checkUndefine(dataObj.params.input);
                utils.checkUndefine(dataObj.params.output);
                utils.checkUndefine(dataObj.params.fee);
                if(!dataObj.params.input ){
                    var err = new Error("Parameter error  "+"input "+dataObj.params.input );
                    throw err;
                }
                if(!dataObj.params.output ){
                    var err = new Error("Parameter error  ;output  : "+dataObj.params.output);
                    throw err;

                }

                if(!dataObj.params.fee){
                    var err = new Error("Parameter error  ; fee:  "+dataObj.params.fee);
                    throw err;
                }


                signBTCTxNEO(dataObj.params.symbol.toLocaleUpperCase(), dataObj.params.input, dataObj.params.output,dataObj.params.fee);

            } else if (dataObj.params.symbol.toLocaleUpperCase() === "LTC") {
                utils.checkUndefine(dataObj.params.input);
                utils.checkUndefine(dataObj.params.output);
                utils.checkUndefine(dataObj.params.privateKey);

                signLTCTx(dataObj.params.privateKey, dataObj.params.input, dataObj.params.output);
            } else {
                return utils.JSONError(logger, 10003, "This currency does not support this feature");
            }
        } else if (dataObj.method === "signMsg") {
            utils.checkUndefine(dataObj.params.symbol);
            utils.checkUndefine(dataObj.params.privateKey);
            if (dataObj.params.symbol.toLocaleUpperCase() === "BTC") {
                signBTCMsg(dataObj.params.privateKey, dataObj.params.message);
            } else if (dataObj.params.symbol.toLocaleUpperCase() === "LTC") {
                signLTCMsg(dataObj.params.privateKey, dataObj.params.message);
            } else {
                return utils.JSONError(logger, 10003, "This currency does not support this feature");
            }
        } else if (dataObj.method === "verifyMsg") {
            utils.checkUndefine(dataObj.params.symbol);
            utils.checkUndefine(dataObj.params.address);
            // utils.checkUndefine(dataObj.params.message);
            utils.checkUndefine(dataObj.params.signature);

            if (dataObj.params.symbol.toLocaleUpperCase() === "BTC") {
                if (!verifyBTCMsg(dataObj.params.message, dataObj.params.address, dataObj.params.signature)) {
                    return utils.JSONError(logger, 10006, "Verification BTC message failed");
                }
            } else if (dataObj.params.symbol.toLocaleUpperCase() === "LTC") {
                if (!verifyLTCMsg(dataObj.params.message, dataObj.params.address, dataObj.params.signature)) {
                    return utils.JSONError(logger, 10006, "Verification LTC message failed");
                }
            } else {
                return utils.JSONError(logger, 10003, "This currency does not support this feature");
            }

        } else if (dataObj.method === "verifyAddress") {
            utils.checkUndefine(dataObj.params.address);
            utils.checkUndefine(dataObj.params.symbol);

            verifyAddress(dataObj.params.symbol.toLocaleUpperCase(), dataObj.params.address);
        } else if(dataObj.method === "countFee"){
            if ((dataObj.params.symbol.toLocaleUpperCase() === "BTC") ||
                (dataObj.params.symbol.toLocaleUpperCase() === "BTC-T")) {


                countFeeCost( dataObj.params.input, dataObj.params.output, dataObj.params.type,dataObj.params.fee);
            }
        }else if(dataObj.method === 'recoverAddress'){
            utils.checkUndefine(dataObj.params.symbol);
            utils.checkUndefine(dataObj.params.mnemonic);
            utils.checkUndefine(dataObj.params.language);
            utils.checkUndefine(dataObj.params.amount);
            utils.checkUndefine(dataObj.params.startIndex);
            recoverAddress(dataObj.params.symbol.toLocaleUpperCase(), dataObj.params.mnemonic, dataObj.params.language, dataObj.params.startIndex * 1, dataObj.params.amount * 1, dataObj.params.additional)
            // console.log(rs);
            // return utils.JSONSuccess("data",rs);
            // generateAddress(dataObj.params.symbol.toLocaleUpperCase(), dataObj.params.mnemonic, dataObj.params.language, dataObj.params.startIndex * 1, dataObj.params.amount * 1, dataObj.params.additional);
        }else{
            return utils.JSONError(logger, 10004, "not found method");
        }

        return utils.JSONSuccess("data", response);
    }
    catch(err) {
        return utils.JSONError(logger, 10002, err.toString());
    }
};
